package org.six11.ss.server;

import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.six11.ss.server.bean.*;
import org.six11.ss.shared.Message;
import org.six11.util.Debug;
import org.six11.util.adt.MultiState;
import org.six11.util.adt.SynchronizedQueue;

/**
 * This is the base class for all the StellaSketch logic handlers, which serve requests like
 * "join a room", "add some pen data" or "list possible themes for this game". Subclasses must
 * implement the aptly named 'handle' method.
 * 
 * Note that the handle function has a hibernate session and transaction that are open, so you don't
 * have to worry about that stuff.
 * 
 * @author Gabe Johnson <johnsogg@cmu.edu>
 */
public abstract class AppletMessageHandler {
  protected MultiState state;

  protected final static SessionFactory sessionFactory;
  protected final static LanguageAnalyzer languageAnalyzer;
  static {
    sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    languageAnalyzer = new LanguageAnalyzer();
  }

  public AppletMessageHandler(MultiState state) {
    this.state = state;
  }

  public abstract void handle(Message m, String sessionID, PrintWriter out);

  public void handleSafely(Message m, String sessionID, PrintWriter out) {
    getSession().setFlushMode(FlushMode.ALWAYS);
    Transaction tx = getSession().beginTransaction();
    try {
      handle(m, sessionID, out);
    } finally {
      tx.commit();
    }
  }

  protected void log(String what) {
    String n = getClass().getName();
    Debug.out(">>  " + n.substring(n.lastIndexOf('.') + 1), what);
  }

  protected void bug(String what) {
    String n = getClass().getName();
    Debug.out(n.substring(n.lastIndexOf('.') + 1), what);
  }

  protected Session getSession() {
    return sessionFactory.getCurrentSession();
  }

  protected SessionFactory getSessionFactory() {
    return sessionFactory;
  }

  protected synchronized void nap(long ms) {
    try {
      wait(ms);
    } catch (InterruptedException ex) {
    }
  }

  /**
   * Gives you a Persistent Person object associated with the given sessionID (or null if the user
   * has not logged in yet).
   * 
   * @param sessionID
   *          a Tomcat session ID.
   * @return a Persistent Person object (or null if the user isn't logged in).
   */
  protected Person getPersonBySession(String sessionID) {
    Person ret = null;
    Map<String, String> map = getSessionToPersonMap();
    if (map.containsKey(sessionID)) {
      ret = loadPerson(map.get(sessionID));
    }
    return ret;
  }

  /**
   * Returns a Persistent Person based on the given ID.
   * 
   * @param id
   *          the primary key for the Person. In the database this is an integer, but I'm trying to
   *          keep all the references to it in the Java code as Strings so I do not have to do
   *          stupid things with parsing.
   * @return a Person object.
   */
  protected Person loadPerson(String id) {
    return (Person) getSession().load(Person.class, Integer.parseInt(id));
  }

  protected Game loadGame(String id) {
    return (Game) getSession().load(Game.class, Integer.parseInt(id));
  }

  protected Room loadRoom(String id) {
    return (Room) getSession().load(Room.class, Integer.parseInt(id));
  }

  protected ThemeOptions loadThemeOptions(String id) {
    return (ThemeOptions) getSession().load(ThemeOptions.class, Integer.parseInt(id));
  }

  protected Theme loadTheme(String id) {
    return (Theme) getSession().load(Theme.class, Integer.parseInt(id));
  }

  protected PhaseTime loadPhaseTime(String name) {
    PhaseTime ret = (PhaseTime) getSession().createQuery("from PhaseTime as pt where name = ?")
        .setString(0, name).uniqueResult();
    return ret;
  }

  protected Round loadRound(String id) {
    return (Round) getSession().load(Round.class, Integer.parseInt(id));
  }

  protected Clue loadClue(String id) {
    return (Clue) getSession().load(Clue.class, Integer.parseInt(id));
  }

  protected Map<String, Long> getLastSeenMap() {
    return (Map<String, Long>) state.getValue(ServerStateNames.SESSION_LAST_SEEN);
  }

  /**
   * Gives the map that relates tomcat session strings to the ID of Person objects.
   * 
   * @return the session->person_id map.
   */
  @SuppressWarnings("unchecked")
  protected Map<String, String> getSessionToPersonMap() {
    return (Map<String, String>) state.getValue(ServerStateNames.SESSION_TO_PERSON);
  }

  protected SynchronizedQueue<Message> getMessageQueue(Person p) {
    SynchronizedQueue<Message> ret = null;
    Map<String, SynchronizedQueue<Message>> queues = getPersonToMessageQueueMap();
    ret = queues.get("" + p.getId());
    return ret;
  }

  @SuppressWarnings("unchecked")
  protected Map<String, SynchronizedQueue<Message>> getPersonToMessageQueueMap() {
    return (Map<String, SynchronizedQueue<Message>>) state
        .getValue(ServerStateNames.PERSON_TO_QUEUE);
  }

  @SuppressWarnings("unchecked")
  protected AppletMessageHandler getHandler(String type) {
    HashMap<String, AppletMessageHandler> appletMessageHandlers = (HashMap<String, AppletMessageHandler>) state
        .getValue(ServerStateNames.HANDLERS);
    return appletMessageHandlers.get(type);
  }

  protected void error(String what) throws Exception {
    bug("Got error and throwing RuntimeException: " + what);
    throw new Exception(what);
  }
}
